/**
 * 
 */
package cz.cuni.mff.abacs.burglar.logics.objects.agents;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import cz.cuni.mff.abacs.burglar.logics.DataMap;
import cz.cuni.mff.abacs.burglar.logics.objects.BaseObject;
import cz.cuni.mff.abacs.burglar.logics.objects.Room;
import cz.cuni.mff.abacs.burglar.logics.objects.items.Inventory;
import cz.cuni.mff.abacs.burglar.logics.objects.items.Item;
import cz.cuni.mff.abacs.burglar.logics.objects.items.Key;
import cz.cuni.mff.abacs.burglar.logics.objects.positions.Door;
import cz.cuni.mff.abacs.burglar.logics.objects.positions.Lockable;
import cz.cuni.mff.abacs.burglar.logics.objects.positions.Position;
import cz.cuni.mff.abacs.burglar.logics.planning.instructions.Instruction;
import cz.cuni.mff.abacs.burglar.visual.Main;


/**
 * 
 * 
 * <ul>
 *   <li>can move on stepable positions</li>
 *   <li>can pick up items</li>
 *   <li>can put down items</li>
 *   <li>can contain items</li>
 *   <li>can lock/unlock doors, boxes</li>
 *   <li>can activate/deactivate positions</li>
 * </ul>
 * 
 * @author abacs
 */
public abstract class BaseAgent extends BaseObject implements Inventory, Agent {
	
		
	// -------------------------------------------------------------------------
	
	
	/** The stepable position where the agent is standing.
	 * 
	 * -1 means not set.
	 * Changeable property. */
	private int _positionId = -1;
	
	/** 
	 * -1 means not set.
	 * Changeable property. */
	private int _roomId = -1;
	
	/** 
	 * Items are immutable.
	 * Changeable property. */
	private final Map<Integer, Item> _inventory = 
		new LinkedHashMap<Integer, Item>();
	
	
	/** Instruction cue to execute. 
	 * 
	 * Instructions are immutable.
	 * Changeable property. */
	protected final List<Instruction> _instructions = 
		new ArrayList<Instruction>();
	
	
	/** 
	 * 
	 * Changeable property. */
	protected final BeliefBase _knowledge = new BeliefBase();
	
	
	/**
	 * 
	 */
	protected GoalBase _goals = null;
	
	
	/** 
	 * 
	 * Changeable property. */
	protected State _state = State.UNSET;
	
	
	// -------------------------------------------------------------------------
	// constructors:
	
	
	/**
	 * 
	 * 
	 * @param id
	 * @param type
	 * @param position
	 * @param goals
	 * @param referenceMap
	 */
	protected BaseAgent(
			int id,
			BaseObject.Type type,
			Position position,
			GoalBase goals,
			DataMap referenceMap
	) {
		super(id, type, referenceMap);
		
		this._positionId = position.getId();
		
		// set the room if possible:
		if(position.getRoom() != null)
			this._roomId = position.getRoom().getId();
		
		this._goals = goals;
	}
	
	
	/**
	 * 
	 * @param original
	 * @param referenceMap
	 */
	protected BaseAgent(BaseAgent original, DataMap referenceMap) {
		super(original.getId(), original.getType(), referenceMap);
		
		this._positionId = original._positionId;
		
		this._roomId = original._roomId;
		
		this._inventory.clear();
		for(Item item : original._inventory.values())
			this._inventory.put(item.getId(), item);
		
		this._instructions.clear();
		for(Instruction instr : original._instructions)
			this._instructions.add(instr);
		
		this._knowledge.copyChangeables(original._knowledge);
		
		this._goals.copyChangeables(original._goals);
		
		this._state = original._state;
	}
	
	// -------------------------------------------------------------------------
	// Inventory related:
	
	
	@Override
	public void addItem(Item item) {
		this._inventory.put(new Integer(item.getId()), item);
	}
	
	
	@Override
	public void addItems(List<Item> items) {
		for(Item item : items){
			if(this.hasItem(item.getId()))
				continue;
			this.addItem(item);
		}
	}
	
	
	@Override
	public Item removeItem(int id) {
		Item ret = this._inventory.get(id);
		this._inventory.remove(id);
		return ret;
	}
	
	
	@Override
	public boolean hasItem(int id) {
		if(this._inventory.get(id) != null)
			return true;
		return false;
	}
	
	
	@Override
	public List<Item> getItems() {
		List<Item> ret = new ArrayList<Item>();
		for(Item item : this._inventory.values()){
			ret.add(item);
		}
		return ret;
	}
	
	
	// -------------------------------------------------------------------------
	// Agent interface:
	
	
	/**
	 * Returns the current position of the agent.
	 * 
	 * @return
	 */
	@Override
	public Position getPosition() {
		return this._referenceMap.getPosition(this._positionId);
	}
	
	
	/**
	 * Returns the id of the current position.
	 * 
	 * @return
	 */
	@Override
	public int getPositionId() {
		return this._positionId;
	}
	
	
	@Override
	public boolean moveTo(Position stepable) {
		
		if(stepable.isTypeOf(BaseObject.Type.DOOR)){
			Door door = (Door)stepable;
			
			if(door.isClosed()){
				System.out.println(
						"- Failure: is locked " + 
						this.getId() + " -> " + stepable.getId()
				);
				this.examine(stepable);
				return false;
			}
			
			System.out.println(
					"- Move " + 
					this.getId() + " -> " + stepable.getId()
			);
			this._positionId = stepable.getId();
			return true;
		}
		
		if(stepable.isTypeOf(BaseObject.Type.FLOOR)){
				
			if(this.getPosition().isTypeOf(BaseObject.Type.DOOR)){
				
				// went into another room:
				Door door = (Door)this.getPosition();
				Room[] rooms = door.getRooms();
				if(rooms[0] == this.getRoom())
					this._roomId = rooms[1].getId();
				else
					this._roomId = rooms[0].getId();
				
				// look around in the new room:
				this.lookAround();
				
				System.out.println(
						"- Room changed " + 
						this.getId() + " -> " + this.getRoom().getId()
				);
			}
			
			System.out.println(
					"- Move " + 
					this.getId() + " -> " + stepable.getId()
			);
			this._positionId = stepable.getId();
			return true;
		}
		
		return false;
	}
	
	
	@Override
	public boolean open(Lockable lockable) {
		boolean result = lockable.open();
		this.examine((Position)lockable);
		
		if(result){
			System.out.println(
					"- Open " + 
					this.getId() + " -> " + ((Position)lockable).getId()
			);
		}else{
			System.out.println(
					"- Failure: is locked " + 
					this.getId() + " -> " + ((Position)lockable).getId()
			);
		}
		
		return result;
	}
	
	
	@Override
	public boolean close(Lockable lockable) {
		boolean result = lockable.close();
		this.examine((Position)lockable);
		
		System.out.println(
				"- Close " + 
				this.getId() + " -> " + ((Position)lockable).getId()
		);
		
		return result;
	}
	
	
	@Override
	public boolean unlock(Lockable lockable, Key key) {
		boolean result = lockable.unlock(key.getId());
		this.examine((Position)lockable);
		
		if(result){
			System.out.println(
					"- Unlock " + 
					this.getId() + " -> " + ((Position)lockable).getId() + 
					"(" + key.getId() + ")"
			);
		}else{
			System.out.println(
					"- Failure: can't unlock " + 
					this.getId() + " -> " + ((Position)lockable).getId()
			);
		}
		return result;
	}
	
	
	@Override
	public boolean lock(Lockable lockable, Key key) {
		boolean result = lockable.lock(key.getId());
		this.examine((Position)lockable);
		
		if(result){
			System.out.println("- Lock " + this.getId() + " -> " + ((Position)lockable).getId() + "(" + key.getId() + ")");
		}else{
			System.out.println("- Failure: can't lock " + this.getId() + " -> " + ((Position)lockable).getId());
		}
		
		return result;
	}
	
	
	@Override
	public boolean pickUp(Inventory inventory, Item item) {
		Item result = inventory.removeItem(item.getId());
		if(result != null)
			this.addItem(item);
		this.examine((Position)inventory);
		
		this._knowledge.forgetItemPosition(item);
		
		if(result != null){
			System.out.println(
					"- PickUp " + 
					this.getId() + " -> " + ((Position)inventory).getId() + 
					"(" + item.getId() + ")"
			);
		}else{
			System.out.println(
					"- Failure: can't pick up " + 
					this.getId() + " -> " + ((Position)inventory).getId() + 
					"(" + item.getId() + ")"
			);
		}
		
		return result != null;
	}
	
	
	@Override
	public Room getRoom() {
		return this._referenceMap.getRoom(this._roomId);
	}
	
	
	@Override
	public int getRoomId() {
		return this._roomId;
	}
	
	
	@Override
	public boolean isInRoom(Room room) {
		return this.isInRoom(room.getId());
	}
	
	
	@Override
	public boolean isInRoom(int roomId) {
		if(this._roomId == roomId)
			return true;
		return this.getPosition().isInRoom(roomId);
	}
	
	
	// -------------------------------------------------------------------------
	// instructions:
	
	
	@Override
	public void addInstruction(Instruction instruction) {
		this._instructions.add(instruction);
	}
	
	
	@Override
	public void addInstructions(List<Instruction> instructions) {
		this._instructions.addAll(instructions);
	}
	
	
	@Override
	public void clearInstructions() {
		this._instructions.clear();
	}
	
	
	@Override
	public List<Instruction> getInstructions() {
		return this._instructions;
	}
	
	
	@Override
	public Instruction popFirstInstruction() {
		if(this._instructions.isEmpty())
			return null;
		return this._instructions.remove(0);
	}
	
	
	// -------------------------------------------------------------------------
	
	
	/**
	 * Returns the rooms that the agent plans to visit.
	 * 
	 * @return
	 */
	public List<Integer> getPlannedRoomIds() {
		List<Integer> rooms = new LinkedList<Integer>();
		int currentRoomId = -1;
		Position subject = null;
		
		for(Instruction instr : this._instructions){
			subject = this._referenceMap.getPosition(instr._subjectId);
			if(subject.getRoomId() != currentRoomId){
				currentRoomId = subject.getRoomId();
				rooms.add(currentRoomId);
			}
		}
		
		return rooms;
	}
	
	
	// -------------------------------------------------------------------------
	
	
	/**   */	
	public State getState() {
		return this._state;
	}
	
	
	// -------------------------------------------------------------------------
	
	
	/**
	 * Looks around in room. Discovers new positions in room.
	 * If the positions were already known, it does not change them.
	 */
	public void lookAround() {
		if(Main.FLAG_LOOK_AROUND)
			this._knowledge.seenFromFar(this.getRoom().getOperablePositions());
	}
	
	
	/**
	 * @param pos
	 */
	public void examine(Position pos) {
		this._knowledge.seenFromNear(pos.examineFromNear());
	}
	
	
	/**
	 * 
	 * @return
	 */
	@Override
	public BeliefBase getBeliefBase() {
		return this._knowledge;
	}
	
	
	// -------------------------------------------------------------------------
	
	
	/**
	 * 
	 * 
	 * @param original
	 */
	protected void copyChangeables(BaseAgent original) {
		
		this._positionId = original._positionId;
		
		this._roomId = original._roomId;
		
		this._inventory.clear();
		for(Item item : original._inventory.values())
			this._inventory.put(item.getId(), item);
		
		this._instructions.clear();
		for(Instruction instr : original._instructions)
			this._instructions.add(instr);
		
		this._knowledge.copyChangeables(original._knowledge);
		
		this._goals.copyChangeables(original._goals);
		
		this._state = original._state;
	}
	
	
}
